package org.oimwrapper.connectors.genericdb;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * Reconciles data from an SQL database with child records into reconciliation events. <p>
 *
 * This task expects the following parameters, in addition to the standard parameters in the 
 * {@link OIMDBUserReconciliation} class.
 * <ul>
 * <li><code>ChildQueryLookup</code> The name of a lookup that maps either the child query or the name of an attribute
 * in which the query can be found to an attribute used to join the child record to the parent record.</li>
 * </ul>
 */
public class OIMDBUserChildReconciliation extends OIMDBUserReconciliation {
	
	/**
	 * A Map of the task attributes holding the query, and name of the key attribute use to join that query in.
	 */
	protected Map childQueries;
	
	/**
	 * This method is called once per task, and should initialize and open a connection to the target system.
	 * 
	 * @exception Exception
	 */
	public void openConnection() throws Exception {
		logger.debug("Entering ::openConnection()");
		super.openConnection();
		childQueries = getLookupValues(getAttribute("ChildQueryLookup"));
		logger.debug("Exiting ::openConnection()");
	}

	/**
	 * This method is called to convert the multi-valued data in the arbitrary Record Object to a Map
	 * containing attribute names as the key, and an Array of Maps for the value.  The Maps in the 
	 * value should contain attribute value pairs for each child record, and one Map should be present
	 * in the array for each child element.
	 * 
	 * @param Record An arbitrary object that contains the data for a single event.
	 * @return A Map containing the attribute value pairs from the record for all multi-valued attributes.
	 * @exception Exception
	 */
	public Map getMultiValuedData(Object Record) throws Exception {
		logger.debug("Entering ::getMultiValuedData()");
		Map result = super.getMultiValuedData(Record);
		for (Iterator iterator = childQueries.keySet().iterator(); iterator.hasNext(); ) {
			String queryAttribute = (String) iterator.next();
			String joinAttribute = (String) childQueries.get(queryAttribute);
			String oimAttribute = getCriticalAttribute(queryAttribute + "Name");
			OIMQuery childQuery = OIMQuery.getQueryAttribute(this, queryAttribute);
			matchToParentRecord((Map) Record, joinAttribute, childQuery);
			OIMConnectionPool.execute(connectionInfo, logger, childQuery, new childQueryProcessor(result, oimAttribute, this));
		}
		logger.debug("Exiting ::getMultiValuedData()");
		return result;
	}
	
	/**
	 * Completes a query to obtain child records for a parent record.  This can be overriden to provide custom
	 * matching logic.
	 * 
	 * @param Record A Map containing the parent data to match to.
	 * @param JoinAttribute The name of the attribute to join on, as per the child record lookup.
	 * @param ChildQuery The query used to get the child table records that needs completion. 
	 * @exception Exception
	 */
	public void matchToParentRecord(Map Record, String JoinAttribute, OIMQuery ChildQuery) throws Exception {
		logger.debug("Entering ::matchToParentRecord()");
		String joinValue = (String) Record.get(JoinAttribute);
		if (joinValue == null) {
			for (Iterator iterator = Record.keySet().iterator(); iterator.hasNext(); ) {
				String attribute = (String) iterator.next();
				if (attribute.equalsIgnoreCase(JoinAttribute)) {
					joinValue = (String) Record.get(attribute);
					break;
				}
			}
		}
		try {
			ChildQuery.set(1, Long.parseLong(joinValue));
		} catch (NumberFormatException e) {
			ChildQuery.set(1, joinValue);
		}
		logger.debug("Exiting ::matchToParentRecord()");
	}
	
	/**
	 * Processes the results from a database child query for the values.
	 */
	protected class childQueryProcessor implements OIMResultProcessor {
		
		/**
		 * A Map of the other child records results processed so far.
		 */
		protected Map result;
		
		/**
		 * The OIM attribute name of the child table being processed.
		 */
		protected String oimAttribute;
		
		/**
		 * The calling task.
		 */
		protected OIMDBUserChildReconciliation caller;
		
		/**
		 * Default Constructor.
		 * 
		 * @param Result A Map of the other child records results processed so far.
		 * @param OimAttribute The OIM attribute name of the child table being processed.
		 * @param Caller The calling task.
		 */
		protected childQueryProcessor(Map Result, String OimAttribute, OIMDBUserChildReconciliation Caller) {
			result = Result;
			oimAttribute = OimAttribute;
			caller = Caller;
		}

		/**
		 * Processes the entries present in a result set.
		 * 
		 * @param Results The result set to process.
		 * @exception Exception
		 */
		public void process(ResultSet Results) throws Exception {
			logger.debug("Entering ::process()");
			startMultiValuedRecords(result, oimAttribute);
			while (Results.next()) {
				ResultSetMetaData columnInfo = Results.getMetaData();
				Map childRecord = new HashMap();
				for (int i = 1; !isStopped() && i <= columnInfo.getColumnCount(); i++) {
					String column = columnInfo.getColumnName(i);
					String value = Results.getString(column) == null ? "" : Results.getString(column);
					if (formatDate && columnInfo.getColumnType(i) == Types.DATE) {
						value = formatDate(Results.getDate(column));
					}
					childRecord.put(column, value);
				}
				addMultiValuedRecord(result, oimAttribute, childRecord);
			}
			logger.debug("Exiting ::process()");
		}
	}
}